﻿using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KongMing.Project.Common.NetExtension
{
    public class NetMemoryCache : INetMemoryCache
    {
        private readonly IMemoryCache MemoryCache;
        public NetMemoryCache(IMemoryCache memoryCache)
        {
            MemoryCache = memoryCache;
        }

        //IQuerable 、 IEnumerable 这些类型会有延迟执行的问题，限制类型不可以是这几个
        private static void ValidateValueType<TResult>()
        {
            Type typeResult = typeof(TResult);
            if (typeResult.IsGenericType)
                typeResult = typeResult.GetGenericTypeDefinition();
            if (typeResult == typeof(IEnumerable<>) || typeResult == typeof(IEnumerable) || typeResult == typeof(IAsyncEnumerable<>) ||
                typeResult == typeof(IQueryable<>) || typeResult == typeof(IQueryable))
                throw new BusinessException("Type not supported. Please use List<T> or T[] instead. ");
        }

        private static void InitCacheEntity(ICacheEntry entry, int baseExpireSeconds)
        {
            //Random.Shared静态方法生成随机数, 设置缓存的过期时间是随机的，防止缓存雪崩情况
            double sec = Random.Shared.Next(baseExpireSeconds, baseExpireSeconds * 2);
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(sec);
        }

        public TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int expireSeconds = 60)
        {
            ValidateValueType<TResult>();
            if (!MemoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = MemoryCache.CreateEntry(cacheKey);
                InitCacheEntity(entry, expireSeconds);
                result = valueFactory(entry);
                entry.Value = result;
            }
            return result;
        }

        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<ICacheEntry, Task<TResult?>> valueFactory, int expireSeconds = 60)
        {
            ValidateValueType<TResult>();
            if (!MemoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = MemoryCache.CreateEntry(cacheKey);
                InitCacheEntity(entry, expireSeconds);
                result = await valueFactory(entry);
                entry.Value = result;
            }
            return result;
        }

        public void Remove(string cacheKey)
        {
            MemoryCache.Remove(cacheKey);
        }
    }
}
